package se.sll.itintegration.catalogue.offer.service;

import java.text.ParseException;
import java.util.*;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.validation.Validator;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;

import lombok.extern.slf4j.Slf4j;
import se.sll.itintegration.catalogue.offer.ejb.OfferEjb;
import se.sll.itintegration.catalogue.offer.entity.ContractEntity;
import se.sll.itintegration.catalogue.offer.entity.OfferEntity;
import se.sll.itintegration.catalogue.offer.entity.ProductReferenceEntity;
import se.sll.service.catalogue.common.ISO8601;
import se.sll.service.catalogue.common.exception.ResourceNotFoundException;
import se.sll.service.catalogue.common.exception.ServiceCatalogueException;
import se.sll.service.catalogue.common.exception.ServiceErrorCode;
import se.sll.service.catalogue.common.exception.ServiceException;
import se.sll.service.catalogue.common.exception.ValidationException;
import se.sll.service.catalogue.common.validation.FacadeRESTValidator;
import se.sll.service.catalogue.offers.api.model.*;
import se.sll.service.catalogue.offers.api.resource.OffersService;

/**
 *
 * @author khaleddaham
 * @author Jiri Uosukainen
 */
@Slf4j
@Stateless
public class OfferService implements OffersService {

    @EJB
    private OfferEjb offerEjb;

    @Inject
    private Validator validator;
    private final FacadeRESTValidator facadeRESTValidator = new FacadeRESTValidator();
    
    private static final String T_MEDIA_TYPE = "application/json; charset=UTF-8";

    /**
     * CRUD Operations
     * @param entity
     * @return 
     * @throws se.sll.service.catalogue.common.exception.ServiceException
     */
    @Override
    public PostUnitsResponse postUnits(CreateOffer entity) throws ServiceException {
        log.debug("create: " + entity);
        List<OfferEntity> existingPs = offerEjb.findByName(entity.getHsaID());
        if (existingPs.isEmpty()) {
            try {
                OfferEntity o = new OfferEntity();
                Date now = new java.util.Date();
                o.setUnitId(entity.getHsaID());
//                o.setOrganisation();
//                o.setRegionalUnitId(entity.getRegionalUnitId());
//                o.setProperties(entity.getProperties());
//                o.setOrganisation(entity.getOrganisation());
                o.setCreationTime(now);
                o.setUpdateTime(now);
                offerEjb.create(o);
            } catch (RuntimeException e) {
                log.error("create() Caught RuntimeException: " + e.getMessage());
                throw new ServiceCatalogueException();
            }
        } else {
            throw new ServiceCatalogueException(); // TODO Add message
        }

        return PostUnitsResponse.created();
    }

    /**
     *
     * @param hsaID
     * @return
     * @throws se.sll.service.catalogue.common.exception.ServiceException
     */
    @Override
    public GetUnitsByHsaIDResponse getUnitsByHsaID(@PathParam("hsaID") String hsaID) throws ServiceException {
        log.debug("read: " + hsaID);
        return GetUnitsByHsaIDResponse.jsonOK(copyOfferEntityToUnit(offerEjb.findByName(hsaID)));
    }

    /**
     *
     * @param oeList
     * @return
     */
    private List<OfferUnitList> copyOfferEntityToUnit(List<OfferEntity> oeList) {
        List<OfferUnitList> unitList = new ArrayList<>();
        for (OfferEntity oe : oeList) {
            OfferUnitList u = new OfferUnitList();
            u.setCreationTime(oe.getCreationTime());
            u.setUnitId(oe.getUnitId());
            //u.setRegionalUnitId(oe.getRegionalUnitId());
            //u.setRelativeDistinguishedName(oe.getRelativeDistinguishedName());
            u.setUpdateTime(oe.getUpdateTime());
            //u.setSourceSystemHsaId(oe.getSourceSystemHsaId());

            u.setProductReferences(new ArrayList<>());
            for (ProductReferenceEntity pre : oe.getProductReferences()) {
                ProductReference pr = new ProductReference();
                pr.setComment(pre.getComment());
                pr.setContractedPrice(pre.getContractedPrice());
                pr.setProduct(null);
                Product p = new Product();
                p.setName(pre.getName());
                pr.setProduct(p);
                u.getProductReferences().add(pr);
            }

            List<Contract> contractList = new ArrayList<>();
            for (ContractEntity ce : oe.getContracts()) {
                Id id = new Id();
                id.setExtension(ce.getContractId());
                id.setRoot("");
                DatePeriod dp = new DatePeriod();
                dp.setStart(ce.getStartDate());
                dp.setEnd(ce.getEndDate());
                Type type = new Type();
                type.setCode(ce.getType().getCode());
                Contract contract = new Contract()
                        .withId(id)
                        .withDatePeriod(dp)
                        .withType(type);
                contractList.add(contract);
                u.setContracts(contractList);
            }
            unitList.add(u);
        }
        return unitList;
    }

    @Override
    public OffersService.PostUnitsByunitidsResponse postUnitsByunitids(UnitList entity) throws ServiceException {
        log.debug("OfferFacadeREST.listByUnitIds(): ENTER");
        long start = System.currentTimeMillis();

        List<OfferEntity> result = null;
        if (entity == null || entity.getUnits() == null || entity.getUnits().isEmpty()) {
            throw new ValidationException(ServiceErrorCode.INVALID_UNIT_IDS_EMPTY);
        }

        try {
            Set<String> input = new HashSet<>();
            for (Unit unit : entity.getUnits()) {
                if (unit.getUnitId() != null) {
                    input.add(unit.getUnitId());
                }
            }
            result = offerEjb.findByUnits(input);
        } catch (RuntimeException e) {
            log.error("OfferFacadeREST.listByUnitIds() Caught Exception: Type: {}, Message: {} ", new Object[]{e.getClass().getName(), e.getMessage()});
            throw new ServiceCatalogueException();
        }
        log.debug("OfferFacadeREST.listByUnitIds(time = {} ms): EXIT", (System.currentTimeMillis() - start));
        return PostUnitsByunitidsResponse.jsonOK(copyOfferEntityToUnit(result));
    }

    /**
     *
     * @param entity
     * @return 
     * @throws se.sll.service.catalogue.common.exception.ValidationException
     * @throws se.sll.service.catalogue.common.exception.ServiceCatalogueException
     * @throws se.sll.service.catalogue.common.exception.ResourceNotFoundException
     */
    @Override
    public OffersService.PutUnitsResponse putUnits(CreateOffer entity) throws ServiceException {
        log.debug("update: " + entity);

        facadeRESTValidator.validate(entity, validator);

        OfferEntity oe = offerEjb.findByUniqueName(entity.getHsaID());
        if (oe != null) {
            try {
                Date now = new java.util.Date();
                //oe.setRegionalUnitId(entity.getRegionalUnitId()); TODO: ??
                oe.setUpdateTime(now);
                offerEjb.edit(oe);
            } catch (RuntimeException e) {
                log.error("ProductFacadeREST.update() Caught RuntimeException: " + e.getMessage());
                throw new ServiceCatalogueException();
            }
        } else {
            throw new ResourceNotFoundException(ServiceErrorCode.OFFER_NOT_FOUND, entity.getHsaID());
        }
        return PutUnitsResponse.jsonOK(entity);
    }

    @Override
    public DeleteUnitsByHsaIDResponse deleteUnitsByHsaID(@PathParam("hsaID") String hsaID) throws ServiceException {
        log.debug("delete: " + hsaID);
        if (hsaID == null) {
            throw new ValidationException(ServiceErrorCode.INVALID_HSA_ID_EMPTY);
        }

        OfferEntity oe = offerEjb.findByUniqueName(hsaID);
        if (oe != null) {
            offerEjb.remove(oe);
        } else {
            throw new ResourceNotFoundException(ServiceErrorCode.OFFER_NOT_FOUND, hsaID);
        }

        return DeleteUnitsByHsaIDResponse.withoutContent();
    }

    /**
     * Find operations
     */
    /**
     * List all offers
     *
     * @return
     * @throws se.sll.service.catalogue.common.exception.ServiceException
     */
    @Override
    public GetUnitsResponse getUnits() throws ServiceException {
        log.debug("OfferService.list() ENTER");

        List<OfferEntity> result = null;
        try {
            result = offerEjb.findAll();
            if (result != null) {
                log.debug("Found {} Offers", new Object[]{result.size()});
            }
        } catch (RuntimeException e) {
            log.error("OfferService.list() Caught RuntimeException: " + e.getMessage());
            throw new ServiceCatalogueException();
        }

        log.debug("OfferService.list() EXIT");
        return GetUnitsResponse.jsonOK(copyOfferEntityToUnit(result));
    }

    /**
     *
     * @param unitId
     * @return
     * @throws se.sll.service.catalogue.common.exception.ServiceException
     */
    @Override
    public GetUnitsByunitidByUnitIdResponse getUnitsByunitidByUnitId(@PathParam("unitId") String unitId) throws ServiceException {
        log.debug("OfferService.findByUnitId(" + unitId + ") ENTER ");

        List<OfferEntity> result = null;
        try {
            result = offerEjb.findByAttributeValue("unitId", unitId);

            if (log.isDebugEnabled() && result != null) {
                log.debug("Found {} Offers", new Object[]{result.size()});
                for (OfferEntity offerEntity : result) {
                    log.debug(offerEntity.toString());
                }
            }
        } catch (RuntimeException e) {
            log.error("OfferService.findByUnitId() Caught RuntimeException: " + e.getMessage());
            throw new ServiceCatalogueException();
        }

        log.debug("OfferService.findByUnitId() EXIT");
        return GetUnitsByunitidByUnitIdResponse.jsonOK(copyOfferEntityToUnit(result));
    }

    @Override
    public GetUnitsByproductnameByProductNameResponse getUnitsByproductnameByProductName(@PathParam("productName") String productName) throws ServiceException {
        log.debug("OfferService.findByProductOrGroupName() ENTER: ");

        List<OfferEntity> result = null;
        try {
            result = offerEjb.findByProductName(productName);
            if (log.isDebugEnabled() && result != null) {
                log.debug("Found {} Offers", new Object[]{result.size()});
            }
        } catch (RuntimeException e) {
            log.error("OfferService.findByProductOrGroupName() Caught RuntimeException: " + e.getMessage());
            throw new ServiceCatalogueException();
        }

        log.debug("OfferService.findByProductOrGroupName() EXIT");
        return GetUnitsByproductnameByProductNameResponse.jsonOK(copyOfferEntityToUnit(result));
    }

    @Override
    public PostUnitsByproductreferencesResponse postUnitsByproductreferences(ProductReferenceParamList entity) throws ServiceException {
        log.debug("OfferService.findByProductReferences() ENTER: ");
        List<OfferEntity> result = null;
        try {
            result = (List<OfferEntity>)offerEjb.findByProductReference((Set<String>) entity);
            if (log.isDebugEnabled() && result != null) {
                log.debug("Found {} Offers", new Object[]{result.size()});
            }
        } catch (RuntimeException e) {
            log.error("OfferService.findByProductReferences() Caught RuntimeException: " + e.getMessage());
            throw new ServiceCatalogueException();
        }
        log.debug("OfferService.findByProductReferences() EXIT");
        return PostUnitsByproductreferencesResponse.jsonOK(copyOfferEntityToUnit(result));
    }

    @Override
    public PostUnitsByHsaIDProductResponse postUnitsByHsaIDProduct(@PathParam("hsaID") String hsaID, @QueryParam("product")
            String product) throws ServiceException {
        log.debug("Adding product: " + product + ", to unitid: " + hsaID);
        List<OfferEntity> existingPs = offerEjb.findByName(hsaID);
        if (!existingPs.isEmpty()) {
            for (ProductReferenceEntity p : existingPs.get(0).getProductReferences()) {
                if (p.getName().equals(product)) {
                    // TODO: Create a new response type for this! "Already existing".
                    return PostUnitsByHsaIDProductResponse.created();
                }
            }
            try {
                ProductReferenceEntity p = new ProductReferenceEntity();

                Date now = new java.util.Date();
                p.setName(product);
                p.setContractedProduct(Boolean.TRUE);
                existingPs.get(0).setUpdateTime(now);
                existingPs.get(0).getProductReferences().add(p);
                p.setOffer(existingPs.get(0));
                offerEjb.createProductReference(p);
                offerEjb.edit(existingPs.get(0));
            } catch (RuntimeException e) {
                log.error("addProductToOffer Caught RuntimeException: " + e.getMessage());
                throw new ServiceCatalogueException();
            }
        } else {
            throw new ServiceCatalogueException(); //TODO Add message
        }

        return PostUnitsByHsaIDProductResponse.created();
    }

    @Override
    public PostUnitsByHsaIDProductsResponse postUnitsByHsaIDProducts(@PathParam("hsaID") String hsaID,
            @QueryParam("product") String product) throws ServiceException {
        List<String> products = Arrays.asList(product.split(","));
        log.debug("Set product: " + product + ", to unitid: " + hsaID);
        List<OfferEntity> existingPs = offerEjb.findByName(hsaID);
        if (!existingPs.isEmpty()) {
            try {
                for (ProductReferenceEntity p : existingPs.get(0).getProductReferences()) {
                    offerEjb.deleteProductReference(p);
                }
                existingPs.get(0).setProductReferences(new HashSet<se.sll.itintegration.catalogue.offer.entity.ProductReferenceEntity>());
//                existingPs = offerEjb.findByName(id);
                Date now = new java.util.Date();
                existingPs.get(0).setUpdateTime(now);
                for (String currentProduct : products) {
                    ProductReferenceEntity p = new ProductReferenceEntity();
                    p.setName(currentProduct);
                    p.setContractedProduct(Boolean.TRUE);
                    existingPs.get(0).getProductReferences().add(p);
                    p.setOffer(existingPs.get(0));
                    offerEjb.createProductReference(p);
                    offerEjb.edit(existingPs.get(0));
                }
            } catch (RuntimeException e) {
                log.error("addProductToOffer Caught RuntimeException: " + e.getMessage());
                throw new ServiceCatalogueException();
            }
        } else {
            throw new ServiceCatalogueException(); //TODO Add message
        }

        return PostUnitsByHsaIDProductsResponse.created();
    }

    @Override
    public PostUnitsByHsaIDContractResponse postUnitsByHsaIDContract(@PathParam("hsaID") String hsaID, @QueryParam("contract") String contract) throws Exception {
        log.debug("Adding contract: " + contract + ", to unitid: " + hsaID);
        List<OfferEntity> existingPs = offerEjb.findByName(hsaID);
        if (!existingPs.isEmpty()) {
            for (ContractEntity c : existingPs.get(0).getContracts()) {
                if (c.getName().equals(contract)) {
                    // TODO: Create a new response type for this.
                    return PostUnitsByHsaIDContractResponse.created();
                }
            }
            try {
                ContractEntity c = new ContractEntity();

                Date now = new java.util.Date();
                c.setName(contract);
                existingPs.get(0).setUpdateTime(now);
                //c.setOffers(existingPs.get(0));
                //offerEjb.createContract(c);
                offerEjb.edit(existingPs.get(0));
            } catch (RuntimeException e) {
                log.error("addContractToOffer Caught RuntimeException: " + e.getMessage());
                throw new ServiceCatalogueException();
            }
        } else {
            throw new ServiceCatalogueException(); //TODO Add message
        }

        return PostUnitsByHsaIDContractResponse.created();
    }

    @Override
    public PostUnitsByHsaIDContractsResponse postUnitsByHsaIDContracts(@PathParam("hsaID") String hsaID, @QueryParam("contract") String contract) throws Exception {
        List<String> contracts = Arrays.asList(contract.split(","));
        log.debug("Set contract list: " + contract + ", to unitid: " + hsaID);
        List<OfferEntity> existingPs = offerEjb.findByName(hsaID);
        if (!existingPs.isEmpty()) {
            try {
                for (ContractEntity p : existingPs.get(0).getContracts()) {
                    //offerEjb.deleteContract(p);
                }
                //existingPs.get(0).setContract(new HashSet<se.sll.itintegration.catalogue.offer.entity.ContractEntity>());
//                existingPs = offerEjb.findByName(id);
                Date now = new java.util.Date();
                existingPs.get(0).setUpdateTime(now);
                for (String currentContracts : contracts) {
                    ContractEntity c = new ContractEntity();
                    c.setName(currentContracts);
                    existingPs.get(0).getContracts().add(c);
                    //c.setOffer(existingPs.get(0));
                    //offerEjb.createContract(c);
                    offerEjb.edit(existingPs.get(0));
                }
            } catch (RuntimeException e) {
                log.error("addProductToOffer Caught RuntimeException: " + e.getMessage());
                throw new ServiceCatalogueException();
            }
        } else {
            throw new ServiceCatalogueException(); //TODO Add message
        }

        return PostUnitsByHsaIDContractsResponse.created();
    }

    /**
     * Returns unit id's for units where offer has been updated after the given
     * timestamp.
     *
     * @param timeStamp The timestamp offset formatted like 'yyyyMMddHHmmss'
     * @return A list of HSAID's, may be empty
     * @throws se.sll.service.catalogue.common.exception.ServiceException
     */
    @Override
    public GetUnitsUpdatedafterByTimeStampResponse getUnitsUpdatedafterByTimeStamp(@PathParam("timeStamp") String timeStamp) throws ServiceException {
        log.debug("OfferFacadeREST.findOffersUpdatedAfter() ENTER:");
        log.debug("Timestamp: " + timeStamp);

        UnitList units = new UnitList();

        if (timeStamp == null) {
            throw new ValidationException(ServiceErrorCode.INVALID_TIME_STAMP_EMPTY);
        }

        try {
            Date offset = ISO8601.formatTime(timeStamp);
            log.debug("Timestamp converted to Date: " + offset);
            List<Unit> unitsIds = getUnitIds(offerEjb.findUpdatedSince(offset));
            units.setUnits(unitsIds);
            log.debug("Found: {} unit(s) with offer updated after {}", new Object[]{unitsIds.size(), offset});
        } catch (RuntimeException e) {
            log.warn("Caught RuntimeException:", e);
            throw new ServiceCatalogueException(); //TODO: Improve message
        } catch (ParseException ex) {
            log.warn("Invalid input, timestamp:" + timeStamp);
        }
        log.debug("OfferFacadeREST.findOffersUpdatedAfter() EXIT:");
        return GetUnitsUpdatedafterByTimeStampResponse.jsonOK(units);
    }

    private List<Unit> getUnitIds(List<OfferEntity> offers) {
        List<Unit> ids = new ArrayList<>();
        if (offers != null) {
            for (OfferEntity offer : offers) {
                if (offer.getUnitId() != null) {
                    ids.add(new Unit().withUnitId(offer.getUnitId()));
                }
            }
        }
        return ids;
    }
}
